#include "CloudiskServer.h"

// 外部接口：为服务器设置所有已实现的路由
void CloudiskServer::register_modules() {
    register_static_resources_module();
    register_signup_module(); 
	register_signin_module();
    register_userinfo_module();
    register_fileupload_module();
    register_filelist_module();
    register_filedownload_module();
}

// ------------------------------------ 设置路由：获取静态资源 ------------------------------------
void CloudiskServer::register_static_resources_module() {
    m_server.GET("/user/signup", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signup.html");
    });

    m_server.GET("/static/view/signin.html", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signin.html");
    });

    m_server.GET("/static/view/home.html", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/home.html");
    });

    m_server.GET("/static/js/auth.js", [](const HttpReq *, HttpResp * resp){
        resp->File("static/js/auth.js");
    });

    m_server.GET("/static/img/avatar.jpeg", [](const HttpReq *, HttpResp * resp){
        resp->File("static/img/avatar.jpeg");
    });

    m_server.GET("/file/upload", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/index.html");
    });

    m_server.Static("/file/upload_files","static/view/upload_files");
}


// ------------------------------------ 设置路由：用户注册 ------------------------------------
void CloudiskServer::register_signup_module() {
    // 注意：该POST的回调，多了一个参数SeriesWork*表示串行流
    m_server.POST("/user/signup", [](const HttpReq* req, HttpResp* POST_resp, SeriesWork* series){
        // 1、校验表单编码格式（内容类型）
        if (req->content_type() != APPLICATION_URLENCODED) {
            POST_resp->set_status(HttpStatusBadRequest);
            POST_resp->append_output_body_nocopy("Unsupported content type");
            return;
        }
        // 2、提取表单信息
        map<string, string>& form = req->form_kv();

        // 3、构建rpc注册请求
        SignRequest signup_req;				
        signup_req.set_username(form["username"]);
        signup_req.set_password(form["password"]);

        // 4、访问注册中心，以获取srpc服务器地址
        string consulUrl = g_consulAddr + "/v1/health/service/" + g_SignService + "?passing=true";
        // 4-1. 创建http任务
        WFHttpTask* task = WFTaskFactory::create_http_task(consulUrl, 2, 2, 
        [signup_req, POST_resp](WFHttpTask* task){
            // a. 提取响应报文体
            const void *body;
            size_t bodysize;
            task->get_resp()->get_parsed_body(&body, &bodysize);
            // 构建json
            nlohmann::json services = nlohmann::json::parse((const char*)body);
            if (services.empty()) {
                POST_resp->set_status_code("500");  // 内部服务器错误
                POST_resp->append_output_body_nocopy("Internal server error");
                cerr << "[注册中心] 无可用服务“注册” " << endl;
                return;
            }

            // b. 提取srpc服务器地址
            auto& serv = services[0];
            auto& info = serv["Service"];
            string srpcIp = info["Address"];          // 目标微服务器ip
            unsigned short srpcPort = info["Port"];   // 目标微服务器port

            // c. 准备rpc调用
            SignupResponse signup_resp;			// rpc响应（传入传出）
            srpc::RPCSyncContext signup_ctx;	// rpc上下文（传入传出）

            // d、调用远程过程 (rpc)
            UserSign::SRPCClient srpcClient{srpcIp.c_str(), srpcPort};
            srpcClient.signup(&signup_req, &signup_resp, &signup_ctx);  // 同步阻塞式
            
            // e、处理rpc响应
            if (signup_ctx.success) { // rpc成功
                POST_resp->set_status_code( std::to_string(signup_resp.status_code()) );
                POST_resp->append_output_body(signup_resp.message()); // 自动设置注册成功/失败消息
            } else { // rpc失败
                POST_resp->set_status_code("500");  // 内部服务器错误
                POST_resp->append_output_body_nocopy("Internal server error");
                cerr << "[RPC error]" << endl
                     << "errcode: " << signup_ctx.error << ", "
                     << "errmsg: " << signup_ctx.errmsg << endl << endl;
            }
        });
        // 4-2. 将 访问注册中心的http任务 加入串行流
        series->push_back(task);
    });
}


// ------------------------------------ 设置路由：用户登录 ------------------------------------
void CloudiskServer::register_signin_module() {
    m_server.POST("/user/signin", [](const HttpReq* req, HttpResp* POST_resp, SeriesWork* series){
        // 1、校验表单格式（内容类型）
        if (req->content_type() != APPLICATION_URLENCODED) {
            POST_resp->set_status(HttpStatusBadRequest);
            POST_resp->append_output_body_nocopy("Unsupported content type");
            return;
        }

        // 2、提取表单信息
        map<string, string>& form = req->form_kv();
        
        // 3、构建rpc登录请求
        SignRequest signin_req;				
        signin_req.set_username(form["username"]);
        signin_req.set_password(form["password"]);

        // 4、访问注册中心，以获取srpc服务器地址
        string consulUrl = g_consulAddr + "/v1/health/service/" + g_SignService + "?passing=true";
        // 4-1. 创建http任务
        WFHttpTask* task = WFTaskFactory::create_http_task(consulUrl, 2, 2, 
        [signin_req, POST_resp](WFHttpTask* task){
            // a. 提取响应报文体
            const void *body;
            size_t bodysize;
            task->get_resp()->get_parsed_body(&body, &bodysize);
            // 构建json
            nlohmann::json services = nlohmann::json::parse((const char*)body);
            if (services.empty()) {
                POST_resp->set_status_code("500");  // 内部服务器错误
                POST_resp->append_output_body_nocopy("Internal server error");
                cerr << "[注册中心] 无可用服务“登录” " << endl;
                return;
            }

            // b. 提取srpc服务器地址
            auto& serv = services[0];
            auto& info = serv["Service"];
            string srpcIp = info["Address"];          // 目标微服务器ip
            unsigned short srpcPort = info["Port"];   // 目标微服务器port

            // c. 准备rpc调用
            SigninResponse signin_resp;			// rpc响应（传入传出）
            srpc::RPCSyncContext signin_ctx;	// rpc上下文（传入传出）

            // d、调用远程过程 (rpc)
            UserSign::SRPCClient srpcClient{srpcIp.c_str(), srpcPort};
            srpcClient.signin(&signin_req, &signin_resp, &signin_ctx);  // 同步阻塞式
            
            // e、处理rpc响应
            if (signin_ctx.success) { // rpc成功
                int status_code = signin_resp.status_code();
                if (status_code == 200) { // 登录成功
                    POST_resp->String(signin_resp.authentication()); // 返回身份验证信息（JSON序列）
                } else { // 登录失败
                    POST_resp->set_status_code( std::to_string(signin_resp.status_code()) );
                    POST_resp->append_output_body(signin_resp.message());
                }
            } else { // rpc失败
                POST_resp->set_status_code("500");  // 内部服务器错误
                POST_resp->append_output_body_nocopy("Internal server error");
                cerr << "[RPC error]" << endl
                     << "errcode: " << signin_ctx.error << ", "
                     << "errmsg: " <<  signin_ctx.errmsg << endl << endl;
            }
        });
        // 4-2. 将 访问注册中心的http任务 加入串行流
        series->push_back(task);
    });
} 


// ----------------------------------- 设置路由：获取用户信息 -----------------------------------
// 以 [url 查询参数]的方式 上传 用户名 和 token
void CloudiskServer::register_userinfo_module() {
    m_server.GET("/user/info", [](const HttpReq* req, HttpResp* resp){
        // 获取 url 中的用户名、token
        string username = req->query("username");
        string token = req->query("token");
        // 验证token
        User user;
        if (!CryptoUtil::verify_token(token, user)) { // 不通过
            resp->set_status_code("401");
            resp->append_output_body_nocopy("Unauthorized! Please try to log in.");
            return;
        }

        // token验证通过，根据用户名查询用户信息（mysql任务）
        string sql = "SELECT username, created_at FROM tbl_user WHERE username='" 
                   + username + "' AND tomb=0;";
        resp->MySQL(g_mysqlURL, sql, [resp](MySQLResultCursor* cursor){
            vector<MySQLCell> record;
            if (cursor->fetch_row(record)) {
                // 以json字符串格式返回：用户名、注册时间
                nlohmann::json retJ = {
                    {"data", {
                        {"Username", record[0].as_string()},
                        {"SignupAt", record[1].as_string()}
                        }
                    }
                };
                 #if DEBUG
                    cout << "retJson:\n" << retJ.dump(2) << endl;
                #endif
                resp->String(retJ.dump());
                return;
            }
            // 数据库查询失败
            resp->set_status_code("500"); // Internal Server Error
        });
    });
}


// ----------------------------------- 设置路由：用户上传文件 -----------------------------------
// 客户端：以 url 查询参数的方式上传用户名和 token；以 form-data 的方式上传文件
void CloudiskServer::register_fileupload_module() {
    m_server.POST("/file/upload", [this](const HttpReq* req, HttpResp* resp){
        // 校验内容类型
        if (req->content_type() != MULTIPART_FORM_DATA) {
            resp->set_status(HttpStatusBadRequest);
            resp->append_output_body_nocopy("Unsupported content type");
            return;
        }
        
        // 获取 url 中的用户名、token
        string username = req->query("username");
        string token = req->query("token");
        
        // 验证token
        User user;
        if (!CryptoUtil::verify_token(token, user)) { // 验证不通过
            resp->set_status_code("401");
            resp->append_output_body_nocopy("Unauthorized! Please try to log in.");
            return;
        }

        // 获取用户id
        int uid = user.id;

        // 获取用户表单数据
        Form& form = req->form();

        // 遍历表单，处理文件上传
        for (auto & [_, file] : form) {
            auto &[fileName, fileContent] = file;
            
            // 根据文件内容生成哈希值
            string hashcode = CryptoUtil::hash_fileContent(fileContent);
            
            // 保存文件元数据到数据库
            char fileSql[1024];
            sprintf(fileSql, "INSERT INTO tbl_file (uid, filename, hashcode, size)"
                                "VALUES(%d, '%s', '%s', %ld);", 
                                uid, fileName.c_str(), hashcode.c_str(), fileContent.size());
            
            #if DEBUG
            cout << "[sql] " << fileSql << endl;
            #endif
            
            // MySQL任务
            resp->MySQL(g_mysqlURL, fileSql, 
            [this, req, resp, hashcode = std::move(hashcode), fileContent = std::move(fileContent)] 
            (MySQLResultCursor* cursor) {
                // sql执行失败 - 用户已拥有同名文件
                if (cursor->get_insert_id() == 0) { 
                    resp->set_status_code("400");  
                    resp->append_output_body_nocopy("duplicationFilename");
                    return;
                }
                
                // sql成功，保存文件:
                string storePath = "./fileStorage/" + hashcode;    // 以哈希值为文件名
                // a. 保存在本地服务器
                int fd = open(storePath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0777);
                write(fd, fileContent.c_str(), fileContent.size());
                close(fd);
                // b. 向消息队列发送备份任务的相关消息
                nlohmann::json mesJ;
                mesJ["filePath"] = storePath;    // 文件所在的相对路径
                mesJ["fileName"] = hashcode;  // 文件保存到OSS时使用的文件名
                this->m_mqProducer.putMessage(mq_exchange, mq_routingKey, mesJ.dump());

                // 重定向  
                resp->set_status_code("303");   
                // 重定向位置
                string redirect = "http://" + req->header("Host") + "/static/view/home.html";             
                resp->add_header_pair("Location", redirect);
            });
        }
    });
}


// ----------------------------------- 设置路由：文件列表查询 -----------------------------------
// 客户端：以 url 查询参数的方式上传用户名，token；
// 客户端：以 x-www-form-urlencoded (表单) 方式上传参数 limit (最多获取多少文件)
void CloudiskServer::register_filelist_module() {
    m_server.POST("/file/query", [](const HttpReq* req, HttpResp* resp){
        // 校验表单格式（内容类型）
        if (req->content_type() != APPLICATION_URLENCODED) {
            resp->set_status(HttpStatusBadRequest);
            resp->append_output_body_nocopy("Unsupported content type");
            return;
        }

        // 获取 url 中的用户名、token
        string username = req->query("username");
        string token = req->query("token");
        // 验证 Token
        User user;
        if (!CryptoUtil::verify_token(token, user)) { // 验证不通过
            resp->set_status_code("401");
            resp->append_output_body_nocopy("Unauthorized! Please try to log in.");
            return;
        }
        // 验证通过，可从user对象中获取用户id

        // 获取表单数据
        map<string, string> form = req->form_kv();
        string limit_str = form["limit"];
        int limit_num;
        #if DEBUG
            cout << "limit_str: " << limit_str << endl;
        #endif
        
        // 校准 limit_num
        if (!limit_str.empty()) {
            limit_num = std::stoi(limit_str);
            if (limit_num <= 0) limit_num = 10; // 防止负数
            if (limit_num > 100) limit_num = 100; // 防止过大
        } else {
            limit_num = 10; // 转换失败使用默认值
        }

        // 根据用户名获取该用户的文件列表 (使用 limit 限制列表的最大长度)
        // sql拼接
        string sql = "SELECT hashcode, filename, size, created_at, last_update "
                     "FROM tbl_file "
                     "WHERE uid=" + std::to_string(user.id) + " AND status = 0 "
                     "ORDER BY last_update DESC "
                     "LIMIT " + std::to_string(limit_num) + ";";
        #if DEBUG
            cout << "[sql]\n" << sql << endl;
        #endif
        resp->MySQL(g_mysqlURL, sql, [resp](MySQLResultCursor* cursor){
            vector<MySQLCell> record;
            nlohmann::json retJ = nlohmann::json::array();  // 空数组
            while (cursor->fetch_row(record)) {
                #if DEBUG
                cout << "FileSize: " << record[2].as_int() << endl;
                #endif
                retJ.push_back(
                    {
                        {"FileHash", record[0].as_string()},
                        {"FileName", record[1].as_string()},
                        {"FileSize", record[2].as_ulonglong()},
                        {"UploadAt", record[3].as_string()},
                        {"LastUpdated", record[4].as_string()}
                    }
                );
            }
            #if DEBUG
                cout << "retJson:\n" << retJ.dump(2) << endl;
            #endif
            resp->String(retJ.dump());
            return;
            // 数据库查询失败
            // resp->set_status_code("500"); // Internal Server Error
        });
    });

}


// ----------------------------------- 设置路由：用户下载文件 -----------------------------------
// 客户端：以 url 查询参数的方式上传文件名，文件哈希，用户名和 token
void CloudiskServer::register_filedownload_module() {
    m_server.GET("/file/download", [](const HttpReq* req, HttpResp* resp){
        // 获取 url 中的query信息
        string username = req->query("username");
        string token = req->query("token");
        string filename = req->query("filename");
        string filehash = req->query("filehash");
        // 验证 Token
        User user;
        if (!CryptoUtil::verify_token(token, user)) { // 验证不通过
            resp->set_status_code("401");
            resp->append_output_body_nocopy("Unauthorized! Please try to log in.");
            return;
        }

        // 返回文件给客户端
        string filepath = "./fileStorage/" + filehash;
        #if DEBUG
            cout  << "filepath: " << filepath << endl;
        #endif
        // 此处应该先验证一下filehash? 懒得写了
        // 执行resp->File()返回指定文件
        resp->File(filepath);
    });
}